package server

import (
	"compress/gzip"
	"net/http"
	"os"
	"path/filepath"
	"sieve_admin_server_userpass/pkg/config"
	"sieve_admin_server_userpass/pkg/json"
	"sieve_admin_server_userpass/pkg/log"
	"sieve_admin_server_userpass/pkg/util/convert"
	"strconv"
	"strings"
	"time"

	"sieve_admin_server_userpass/pkg/im3/app/auth"
)

var (
	fPort          = config.Int("server.port", 9900, "http port")
	fTlsCert       = config.String("tls.cert", "", "tls cert file")
	fTlsKey        = config.String("tls.key", "", "tls key file")
	fWebRoot       = config.String("web.root", filepath.Join(filepath.Dir(os.Args[0]), "../www"), "web root")
	fWebStatic     = config.Strings("web.static", []string{"static"}, "web static dirs")
	fWebVersion    = config.String("web.version", "", "web version")
	fPathRoot      = config.String("web.pathroot", "/", "path root")
	fGzipThreshold = config.Int("gzip.threshold", 2048, "返回大于多少字节时开启gzip压缩")
	fGzipLevel     = config.Int("gzip.level", gzip.DefaultCompression, "gzip压缩等级")
	fDebugMode     = config.Bool("debug.enable", false, "debug模式")
	fDebugSession  = config.String("debug.session", "", "debug模式万能session")
	pId            = config.Int("server.pid", 1, "")
	fId            = config.Int("server.fid", 1, "")
)

func Run() error {
	http.HandleFunc("/login", handleLogin)
	http.HandleFunc("/logout", handleLogout)
	http.HandleFunc("/deploy", handleDeploy)
	for _, p := range *fWebStatic {
		if p != "" {
			http.HandleFunc("/"+p+"/", func(w http.ResponseWriter, r *http.Request) {
				http.ServeFile(w, r, filepath.Join(*fWebRoot, r.URL.Path[1:]))
			})
		}
	}
	http.HandleFunc("/", handleHttp)
	sv := &http.Server{Addr: ":" + strconv.Itoa(*fPort)}
	sv.SetKeepAlivesEnabled(false)
	if *fTlsCert != "" && *fTlsKey != "" {
		return sv.ListenAndServeTLS(*fTlsCert, *fTlsKey)
	} else {
		return sv.ListenAndServe()
	}
}

func DebugMode() bool { return *fDebugMode }

func handleHttp(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	now := time.Now()

	if !strings.HasPrefix(r.URL.Path, *fPathRoot) {
		http.NotFound(w, r)
		return
	}
	ss := strings.Split(r.URL.Path[len(*fPathRoot):], "/")
	mod := ss[0]
	api := ""
	if len(ss) > 1 {
		mod = strings.Join(ss[:len(ss)-1], "/")
		api = ss[len(ss)-1]
	}

	if mod == "" {
		if GetLoginUid(r) == 0 {
			auth.ShowAuthPage(w, r)
		} else {
			handleIndex(w, r)
		}
		return
	}

	if !*fDebugMode && r.Method != http.MethodPost {
		w.WriteHeader(http.StatusForbidden)
		w.Write([]byte("POST Only"))
		return
	}

	context := &Context{
		Args: r.PostForm,
		Http: &ContextHttp{w, r},
	}
	if strings.HasPrefix(api, "upload_") {
		err := r.ParseMultipartForm(32 << 20)
		if err != nil {
			w.Write([]byte("{\"Code\":4000,\"ErrMsg\":\"parse form wrong\"}"))
			return
		}
		context.Args = r.MultipartForm.Value
	} else {
		if err := r.ParseForm(); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			w.Write([]byte(err.Error()))
			return
		}
		context.Args = r.PostForm
	}

	if *fWebVersion != "" && context.Args.Get("currVer") != *fWebVersion {
		w.WriteHeader(http.StatusForbidden)
		w.Write([]byte("有新版更新，请刷新浏览器后尝试！"))
		return
	}

	uid := GetLoginUid(r)
	if uid == 0 {
		w.WriteHeader(http.StatusForbidden)
		w.Write([]byte("not login"))
		return
	}
	m, ok := mods[mod]
	if !ok {
		http.NotFound(w, r)
		return
	}
	fn, ok := m.apis[api]
	if !ok {
		http.NotFound(w, r)
		return
	}

	perm, err := parseApiPerm(api)
	if err != nil {
		w.WriteHeader(http.StatusForbidden)
		w.Write([]byte("invalid api format"))
		return
	}
	perm = mod + "." + perm

	if !checkUserPerm(uid, perm) {
		w.WriteHeader(http.StatusForbidden)
		w.Write([]byte("permission denied"))
		return
	}

	defer addOpLog(uid, r, now)
	context.Uid = uid
	context.Args.Set("Pid", convert.ToString(*pId))
	context.Args.Set("Fid", convert.ToString(*fId))
	ret := fn(context)
	if ret == nil {
		return
	}
	if err, ok := ret.(error); ok {
		log.Err(r.URL.Path, err)
		w.WriteHeader(http.StatusForbidden)
		w.Write([]byte(err.Error()))
		return
	}
	var b []byte
	switch v := ret.(type) {
	case string:
		b = []byte(v)
	default:
		b, _ = json.Marshal(v)
	}
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	if len(b) > *fGzipThreshold && *fGzipLevel != gzip.NoCompression && strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
		w.Header().Set("Content-Encoding", "gzip")
		gw, _ := gzip.NewWriterLevel(w, *fGzipLevel)
		defer gw.Close()
		gw.Write(b)
	} else {
		w.Write(b)
	}
}

func handleIndex(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, filepath.Join(*fWebRoot, "index.html"))
}

func GetWebRootPath() string {
	return *fWebRoot
}
